package org.bukkit.craftbukkit.inventory;

import com.google.common.collect.ImmutableSet;

import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEvent.ShowItem;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.hover.content.Content;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.ItemEnchantmentsComponent;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.nbt.NbtOps;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntryList;
import net.minecraft.registry.entry.RegistryEntryList.Named;
import net.minecraft.registry.tag.EnchantmentTags;

import java.util.Collection;
import java.util.Optional;
import java.util.Random;
import java.util.function.UnaryOperator;

import org.apache.commons.lang.Validate;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.util.CraftLegacy;
import org.bukkit.craftbukkit.util.RandomSourceWrapper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.cardboardpowered.impl.world.WorldImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;

public final class CraftItemFactory implements ItemFactory {

	private static final net.minecraft.util.math.random.Random randomSource = net.minecraft.util.math.random.Random.create();
	
    protected static final Color DEFAULT_LEATHER_COLOR = Color.fromRGB(0xA06540);
    protected static final Collection<String> KNOWN_NBT_ATTRIBUTE_NAMES;
    private static final CraftItemFactory instance;

    static {
        instance = new CraftItemFactory();
        ConfigurationSerialization.registerClass(CraftMetaItem.SerializableMeta.class);
        KNOWN_NBT_ATTRIBUTE_NAMES = ImmutableSet.<String>builder()
            .add("generic.armor")
            .add("generic.armorToughness")
            .add("generic.attackDamage")
            .add("generic.followRange")
            .add("generic.knockbackResistance")
            .add("generic.maxHealth")
            .add("generic.movementSpeed")
            .add("generic.flyingSpeed")
            .add("generic.attackSpeed")
            .add("generic.luck")
            .add("horse.jumpStrength")
            .add("zombie.spawnReinforcements")
            .add("generic.attackKnockback")
            .build();
    }

    private CraftItemFactory() {
    }

    @Override
    public boolean isApplicable(ItemMeta meta, ItemStack itemstack) {
        return itemstack == null ? false : isApplicable(meta, itemstack.getType());
    }

    @Override
    public boolean isApplicable(ItemMeta meta, Material type) {
        type = CraftLegacy.fromLegacy(type); // This may be called from legacy item stacks, try to get the right material
        if (type == null || meta == null)
            return false;

        if (!(meta instanceof CraftMetaItem))
            throw new IllegalArgumentException(meta.getClass().toString() + "'s meta not created by " + CraftItemFactory.class.getName());

        return ((CraftMetaItem) meta).applicableTo(type);
    }

    @Override
    public ItemMeta getItemMeta(Material material) {
        return getItemMeta(material, null);
    }

    private ItemMeta getItemMeta(Material material, CraftMetaItem meta) {
        material = CraftLegacy.fromLegacy(material); // This may be called from legacy item stacks, try to get the right material
        switch (material) {
            case AIR:
                return null;
            case WRITTEN_BOOK:
                return meta instanceof CraftMetaBookSigned ? meta : new CraftMetaBookSigned(meta);
            case WRITABLE_BOOK:
                return meta != null && meta.getClass().equals(CraftMetaBook.class) ? meta : new CraftMetaBook(meta);
            case CREEPER_HEAD:
            case CREEPER_WALL_HEAD:
            case DRAGON_HEAD:
            case DRAGON_WALL_HEAD:
            case PLAYER_HEAD:
            case PLAYER_WALL_HEAD:
            case SKELETON_SKULL:
            case SKELETON_WALL_SKULL:
            case WITHER_SKELETON_SKULL:
            case WITHER_SKELETON_WALL_SKULL:
            case ZOMBIE_HEAD:
            case ZOMBIE_WALL_HEAD:
                return meta instanceof CraftMetaSkull ? meta : new CraftMetaSkull(meta);
            case LEATHER_HELMET:
            case LEATHER_HORSE_ARMOR:
            case LEATHER_CHESTPLATE:
            case LEATHER_LEGGINGS:
            case LEATHER_BOOTS:
                return meta instanceof CraftMetaLeatherArmor ? meta : new CraftMetaLeatherArmor(meta);
            case POTION:
            case SPLASH_POTION:
            case LINGERING_POTION:
            case TIPPED_ARROW:
                return meta instanceof CraftMetaPotion ? meta : new CraftMetaPotion(meta);
            case FILLED_MAP:
                return meta instanceof CraftMetaMap ? meta : new CraftMetaMap(meta);
            case FIREWORK_ROCKET:
                return meta instanceof CraftMetaFirework ? meta : new CraftMetaFirework(meta);
            case FIREWORK_STAR:
                return meta instanceof CraftMetaCharge ? meta : new CraftMetaCharge(meta);
            case ENCHANTED_BOOK:
                return meta instanceof CraftMetaEnchantedBook ? meta : new CraftMetaEnchantedBook(meta);
            case BLACK_BANNER:
            case BLACK_WALL_BANNER:
            case BLUE_BANNER:
            case BLUE_WALL_BANNER:
            case BROWN_BANNER:
            case BROWN_WALL_BANNER:
            case CYAN_BANNER:
            case CYAN_WALL_BANNER:
            case GRAY_BANNER:
            case GRAY_WALL_BANNER:
            case GREEN_BANNER:
            case GREEN_WALL_BANNER:
            case LIGHT_BLUE_BANNER:
            case LIGHT_BLUE_WALL_BANNER:
            case LIGHT_GRAY_BANNER:
            case LIGHT_GRAY_WALL_BANNER:
            case LIME_BANNER:
            case LIME_WALL_BANNER:
            case MAGENTA_BANNER:
            case MAGENTA_WALL_BANNER:
            case ORANGE_BANNER:
            case ORANGE_WALL_BANNER:
            case PINK_BANNER:
            case PINK_WALL_BANNER:
            case PURPLE_BANNER:
            case PURPLE_WALL_BANNER:
            case RED_BANNER:
            case RED_WALL_BANNER:
            case WHITE_BANNER:
            case WHITE_WALL_BANNER:
            case YELLOW_BANNER:
            case YELLOW_WALL_BANNER:
                return meta instanceof CraftMetaBanner ? meta : new CraftMetaBanner(meta);
            case BAT_SPAWN_EGG:
            case BEE_SPAWN_EGG:
            case BLAZE_SPAWN_EGG:
            case CAT_SPAWN_EGG:
            case CAVE_SPIDER_SPAWN_EGG:
            case CHICKEN_SPAWN_EGG:
            case COD_SPAWN_EGG:
            case COW_SPAWN_EGG:
            case CREEPER_SPAWN_EGG:
            case DOLPHIN_SPAWN_EGG:
            case DONKEY_SPAWN_EGG:
            case DROWNED_SPAWN_EGG:
            case ELDER_GUARDIAN_SPAWN_EGG:
            case ENDERMAN_SPAWN_EGG:
            case ENDERMITE_SPAWN_EGG:
            case EVOKER_SPAWN_EGG:
            case FOX_SPAWN_EGG:
            case GHAST_SPAWN_EGG:
            case GUARDIAN_SPAWN_EGG:
            case HORSE_SPAWN_EGG:
            case HUSK_SPAWN_EGG:
            case LLAMA_SPAWN_EGG:
            case MAGMA_CUBE_SPAWN_EGG:
            case MOOSHROOM_SPAWN_EGG:
            case MULE_SPAWN_EGG:
            case OCELOT_SPAWN_EGG:
            case PANDA_SPAWN_EGG:
            case PARROT_SPAWN_EGG:
            case PHANTOM_SPAWN_EGG:
            case PIG_SPAWN_EGG:
            case PILLAGER_SPAWN_EGG:
            case POLAR_BEAR_SPAWN_EGG:
            case PUFFERFISH_SPAWN_EGG:
            case RABBIT_SPAWN_EGG:
            case RAVAGER_SPAWN_EGG:
            case SALMON_SPAWN_EGG:
            case SHEEP_SPAWN_EGG:
            case SHULKER_SPAWN_EGG:
            case SILVERFISH_SPAWN_EGG:
            case SKELETON_HORSE_SPAWN_EGG:
            case SKELETON_SPAWN_EGG:
            case SLIME_SPAWN_EGG:
            case SPIDER_SPAWN_EGG:
            case SQUID_SPAWN_EGG:
            case STRAY_SPAWN_EGG:
            case TRADER_LLAMA_SPAWN_EGG:
            case TROPICAL_FISH_SPAWN_EGG:
            case TURTLE_SPAWN_EGG:
            case VEX_SPAWN_EGG:
            case VILLAGER_SPAWN_EGG:
            case VINDICATOR_SPAWN_EGG:
            case WANDERING_TRADER_SPAWN_EGG:
            case WITCH_SPAWN_EGG:
            case WITHER_SKELETON_SPAWN_EGG:
            case WOLF_SPAWN_EGG:
            case ZOMBIE_HORSE_SPAWN_EGG:
            case PIGLIN_SPAWN_EGG:
            case ZOMBIE_SPAWN_EGG:
            case ZOMBIE_VILLAGER_SPAWN_EGG:
                return meta instanceof CraftMetaSpawnEgg ? meta : new CraftMetaSpawnEgg(meta);
            case ARMOR_STAND:
                return meta instanceof CraftMetaArmorStand ? meta : new CraftMetaArmorStand(meta);
            case KNOWLEDGE_BOOK:
                return meta instanceof CraftMetaKnowledgeBook ? meta : new CraftMetaKnowledgeBook(meta);
            case FURNACE:
            case CHEST:
            case TRAPPED_CHEST:
            case JUKEBOX:
            case DISPENSER:
            case DROPPER:
            case ACACIA_SIGN:
            case ACACIA_WALL_SIGN:
            case BIRCH_SIGN:
            case BIRCH_WALL_SIGN:
            case DARK_OAK_SIGN:
            case DARK_OAK_WALL_SIGN:
            case JUNGLE_SIGN:
            case JUNGLE_WALL_SIGN:
            case OAK_SIGN:
            case OAK_WALL_SIGN:
            case SPRUCE_SIGN:
            case SPRUCE_WALL_SIGN:
            case SPAWNER:
            case BREWING_STAND:
            case ENCHANTING_TABLE:
            case COMMAND_BLOCK:
            case REPEATING_COMMAND_BLOCK:
            case CHAIN_COMMAND_BLOCK:
            case BEACON:
            case DAYLIGHT_DETECTOR:
            case HOPPER:
            case COMPARATOR:
            case SHIELD:
            case STRUCTURE_BLOCK:
            case SHULKER_BOX:
            case WHITE_SHULKER_BOX:
            case ORANGE_SHULKER_BOX:
            case MAGENTA_SHULKER_BOX:
            case LIGHT_BLUE_SHULKER_BOX:
            case YELLOW_SHULKER_BOX:
            case LIME_SHULKER_BOX:
            case PINK_SHULKER_BOX:
            case GRAY_SHULKER_BOX:
            case LIGHT_GRAY_SHULKER_BOX:
            case CYAN_SHULKER_BOX:
            case PURPLE_SHULKER_BOX:
            case BLUE_SHULKER_BOX:
            case BROWN_SHULKER_BOX:
            case GREEN_SHULKER_BOX:
            case RED_SHULKER_BOX:
            case BLACK_SHULKER_BOX:
            case ENDER_CHEST:
            case BARREL:
            case BELL:
            case BLAST_FURNACE:
            case CAMPFIRE:
            case JIGSAW:
            case LECTERN:
            case SMOKER:
            case BEEHIVE:
            case BEE_NEST:
                return new CraftMetaBlockState(meta, material);
            case TROPICAL_FISH_BUCKET:
                // TODO return meta instanceof CraftMetaTropicalFishBucket ? meta : new CraftMetaTropicalFishBucket(meta);
            case CROSSBOW:
                return meta instanceof CraftMetaCrossbow ? meta : new CraftMetaCrossbow(meta);
            case SUSPICIOUS_STEW:
                return meta instanceof CraftMetaSuspiciousStew ? meta : new CraftMetaSuspiciousStew(meta);
            default:
                return new CraftMetaItem(meta);
        }
    }

    @Override
    public boolean equals(ItemMeta meta1, ItemMeta meta2) {
        if (meta1 == meta2) return true;

        if (meta1 != null && !(meta1 instanceof CraftMetaItem))
            throw new IllegalArgumentException("First meta of " + meta1.getClass().getName() + " does not belong to " + CraftItemFactory.class.getName());

        if (meta2 != null && !(meta2 instanceof CraftMetaItem))
            throw new IllegalArgumentException("Second meta " + meta2.getClass().getName() + " does not belong to " + CraftItemFactory.class.getName());

        if (meta1 == null) return ((CraftMetaItem) meta2).isEmpty();
        if (meta2 == null) return ((CraftMetaItem) meta1).isEmpty();

        return equals((CraftMetaItem) meta1, (CraftMetaItem) meta2);
    }

    public boolean equals(CraftMetaItem meta1, CraftMetaItem meta2) {
        return meta1.equalsCommon(meta2) && meta1.notUncommon(meta2) && meta2.notUncommon(meta1);
    }

    public static CraftItemFactory instance() {
        return instance;
    }

    @Override
    public ItemMeta asMetaFor(ItemMeta meta, ItemStack stack) {
        Validate.notNull(stack, "Stack cannot be null");
        return asMetaFor(meta, stack.getType());
    }

    @Override
    public ItemMeta asMetaFor(ItemMeta meta, Material material) {
        Validate.notNull(material, "Material cannot be null");
        if (!(meta instanceof CraftMetaItem))
            throw new IllegalArgumentException("Meta of " + (meta != null ? meta.getClass().toString() : "null") + " not created by " + CraftItemFactory.class.getName());
        return getItemMeta(material, (CraftMetaItem) meta);
    }

    @Override
    public Color getDefaultLeatherColor() {
        return DEFAULT_LEATHER_COLOR;
    }

    // @Override
    public Material updateMaterial(ItemMeta meta, Material material) throws IllegalArgumentException {
        return ((CraftMetaItem) meta).updateMaterial(material);
    }

    @Override
    public ItemStack ensureServerConversions(ItemStack arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getI18NDisplayName(ItemStack arg0) {
        // TODO Auto-generated method stub
        return arg0.getType().name();
    }

    @Override
    public Content hoverContentOf(ItemStack arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Content hoverContentOf(Entity arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Content hoverContentOf(Entity arg0, String arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Content hoverContentOf(Entity arg0, BaseComponent arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Content hoverContentOf(Entity arg0, BaseComponent[] arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public HoverEvent<ShowItem> asHoverEvent(@NotNull ItemStack arg0, @NotNull UnaryOperator<ShowItem> arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Component displayName(@NotNull ItemStack arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public @Nullable Material getSpawnEgg(EntityType arg0) {
    // public @Nullable ItemStack getSpawnEgg(EntityType arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    
    // 1.18.2 api:

	@Override
	public @NotNull ItemStack createItemStack(@NotNull String arg0) throws IllegalArgumentException {
		// TODO Auto-generated method stub
		return null;
	}

	// 1.20.2 API:

	@Override
    public ItemStack enchantItem(Entity entity, ItemStack itemStack, int level, boolean allowTreasures) {
		
        return CraftItemFactory.enchantItem(CraftItemFactory.randomSource, itemStack, level, allowTreasures);

		// TODO
        // return CraftItemFactory.enchantItem(((CraftEntity) entity).getHandle().random, itemStack, level, allowTreasures);
    }

    @Override
    public ItemStack enchantItem(final World world, final ItemStack itemStack, final int level, final boolean allowTreasures) {
        return CraftItemFactory.enchantItem(((WorldImpl) world).getHandle().random, itemStack, level, allowTreasures);
    }

    @Override
    public ItemStack enchantItem(final ItemStack itemStack, final int level, final boolean allowTreasures) {
        return CraftItemFactory.enchantItem(CraftItemFactory.randomSource, itemStack, level, allowTreasures);
    }

    private static ItemStack enchantItem(net.minecraft.util.math.random.Random source, ItemStack itemStack, int level, boolean allowTreasures) {
        itemStack = CraftItemStack.asCraftCopy(itemStack);
        CraftItemStack craft = (CraftItemStack) itemStack;
        DynamicRegistryManager registry = CraftRegistry.getMinecraftRegistry();
        Optional<RegistryEntryList.Named<Enchantment>> optional = (allowTreasures) ? Optional.empty() : registry.getOrThrow(RegistryKeys.ENCHANTMENT).getOptional(EnchantmentTags.IN_ENCHANTING_TABLE);
        return CraftItemStack.asCraftMirror(EnchantmentHelper.enchant(source, craft.handle, level, registry, optional));
    }

	@Override
    public ItemStack enchantWithLevels(ItemStack itemStack, int levels, boolean allowTreasure, Random random) {
        return this.enchantWithLevels(itemStack, levels, allowTreasure ? Optional.empty() : CraftServer.server.getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT).getOptional(EnchantmentTags.IN_ENCHANTING_TABLE), random);
    }

	@Override
    public ItemStack enchantWithLevels(ItemStack itemStack, int levels, RegistryKeySet<org.bukkit.enchantments.Enchantment> keySet, Random random) {
		return this.enchantWithLevels(itemStack, levels, Optional.of(PaperRegistrySets.convertToNms(RegistryKeys.ENCHANTMENT, CraftServer.server.getRegistryManager().getOps(NbtOps.INSTANCE).registryInfoGetter, keySet)), random);
    }

    private ItemStack enchantWithLevels(ItemStack itemStack, int levels, Optional<? extends RegistryEntryList<net.minecraft.enchantment.Enchantment>> possibleEnchantments, Random random) {
        net.minecraft.item.ItemStack internalStack = CraftItemStack.asNMSCopy(itemStack);
        if (internalStack.hasEnchantments()) {
            internalStack.set(DataComponentTypes.ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT);
        }
        DynamicRegistryManager.Immutable registryAccess = CraftServer.server.getRegistryManager();
        net.minecraft.item.ItemStack enchanted = EnchantmentHelper.enchant(new RandomSourceWrapper(random), internalStack, levels, registryAccess, possibleEnchantments);
        return CraftItemStack.asCraftMirror(enchanted);
    }

}